(function(window, document, angular){
	var md = angular.module('pui', []);

	md.service('styleHelper', function(){
		this.addHover = function($el){
			$el.on('mouseenter', function(){
				if(!$el.is('.ui-state-active') && !$el.is('.ui-state-disabled')) 
					$el.addClass('ui-state-hover');
			}).on('mouseleave', function(){
				$el.removeClass('ui-state-hover');
			});
		};

		this.delegateHover = function($el, selector){
			$el.delegate(selector, 'mouseenter', function(e){
				var hovered = $(e.target);
				if(!hovered.is(selector))
					hovered = hovered.closest(selector);

				$el.find(selector + '.ui-state-hover').removeClass('ui-state-hover');

				if(!hovered.is('.ui-state-active') && !hovered.is('.ui-state-disabled'))
					hovered.addClass('ui-state-hover');
			}).on('mouseleave', function(){
				$el.find(selector).removeClass('ui-state-hover');
			});
		};

		this.delegateClick = function(selector, attrId){
			$(document).click(function(e){
				var target = $(e.target);
				if(!target.is(selector)){
					target = target.closest(selector);
				}

				if(!target.length){
					$(selector).triggerHandler('propagation.hide');
				}else{
					var targetId = target.attr(attrId);
					$(selector).each(function(){
						var one = $(this);
						if(targetId !== one.attr(attrId))
							one.triggerHandler('propagation.hide');
					});
				}
			});
		};
	});

	md.directive('puiAccordion', function(){
		return {
			scope: true, 
			transclude: true, 

			templateUrl: 'tpl/accordion.html', 

			controller: function($scope, $element, $attrs){
				var opts = $scope.$eval($attrs.puiAccordion) || {};
				var multiple = !!opts.multiple;
				var activeIndexList = opts.activeIndexList || [opts.activeIndex || 0];

				var panes = $scope.panes = [];

				this.focus = function(pane){
					if(!multiple){
						_.each(panes, function(it){
							it.isShow = false;
						});
					}
					pane.isShow = true;
				};

				this.add = function(pane){
					panes.push(pane);
					if(panes.length == 1){
						pane.isFirst = true;
					}
					if(activeIndexList.indexOf(panes.length - 1) >= 0){
						pane.isShow = true;
					}
				};
			}
		};
	});

	md.directive('puiAccordionPane', function(){
		return {
			require: '^puiAccordion',

			scope: {
				title: '@'
			}, 
			transclude: true, 

			templateUrl: 'tpl/accordion-pane.html', 

			link: function(scope, el, attrs, ctrl){
				ctrl.add(scope);

				scope.changeAsCurrent = function(){
					var isShow = scope.isShow = !scope.isShow;
					if(isShow){
						ctrl.focus(scope);
					}
				};
			}
		};
	});
	
	md.service('puiAutocompleteHelper', function(styleHelper){
		// panel begin zindex
		this.zindex = 1000;
		this.defaultPanelScrollHeight = 150;
		this.highlightClass = 'ui-state-highlight';
		this.hoverClass = 'ui-state-hover';

		// set autocomplet result list div position due to element offset
		// need add window.resize event listener
		this.setPanelPosition = function($panel, $el, opts){
			var offsetEl = $el;

			var multipleWrapper = $el.closest('.pui-autocomplete-multiple');
			if(multipleWrapper.length)
				offsetEl = multipleWrapper;

			var offset = offsetEl.offset();
			$panel.css({
				zindex: this.zindex++, 
				width: offsetEl.width(), 
				top: offset.top + offsetEl.height(), 
				left: offset.left
			});

			var height = (opts.height || this.defaultPanelScrollHeight) + 'px';
			if(opts.dropdown){
				$panel.find('.pui-dropdown-items-wrapper').css({height: height});
			}else{
				$panel.css({height: height});
			}
		};

		this.bindPanelEvent = function($panel){
			styleHelper.delegateHover($panel, 'li');
		};

		this.bindDelegateDocumentEvent = function(attrId){
			$(document).click(function(e){
				var target = $(e.target);
				if(!target.is('[pui-autocomplete]') && !target.is('.pui-dropdown-filter')){
					$('.pui-autocomplete-panel, .pui-dropdown-panel').hide();

					// multiple choosed label delete span
					// is a span <ul><li><span></span></li></ul>
					if(target.is('[data-pui-ac-close-span]')){
						var acId = target.attr('data-pui-ac-close-span');
						$('.pui-autocomplete-panel').each(function(){
							var panel = $(this);
							var panelAcId = panel.attr(attrId);
							if(acId === panelAcId){
								var targetVal = target.parent().attr('data-raw-value');
								panel.triggerHandler('puiAcDelVal', [targetVal]);
							}
						});

						target.parent().remove();
					}
				}else{
					// hide others panel
					var acId = target.attr(attrId);
					$('.pui-autocomplete-panel, .pui-dropdown-panel').each(function(){
						var panel = $(this);
						var panelAcId = panel.attr(attrId);
						if(acId !== panelAcId)
							panel.triggerHandler('puiAcHide');
					});
				}
			});
		};

		this.focusByCalIndex = function(panel, calFn){
			var liList = panel.find('li');
			var liCurrent = liList.filter('.' + this.hoverClass);
			liCurrent.removeClass(this.hoverClass);

			var index = liList.index(liCurrent);

			var targetIndex = calFn(index, liList.length);
			liList.eq(targetIndex).addClass(this.hoverClass);
		};

		this.focusPrev = function(panel){
			this.focusByCalIndex(panel, function(index, len){
				return index <= 0 ? len - 1 : index - 1;
			});
		};

		this.focusNext = function(panel){
			this.focusByCalIndex(panel, function(index, len){
				return index < len - 1 ? index + 1 : 0;
			});
		};

		this.wrapMultipleLi = function(targetVal, acId){
			return '<li data-raw-value="' + targetVal + 
				'" class="pui-autocomplete-token ui-state-active ui-corner-all ui-helper-hidden" ' + 
				'style="display: list-item;"><span data-pui-ac-close-span="' + acId + 
				'" class="pui-autocomplete-token-icon ui-icon ui-icon-close"></span>' + 
				'<span class="pui-autocomplete-token-label">' + targetVal + '</span></li>';
		};
	});

	md.directive('puiAutocomplete', function($parse, $compile, puiAutocompleteHelper){
		var countNum = 0;
		var attrId = 'data-pui-ac-id';
		var defaultOptions = {
			height: 200, 
			multiple: false, 
			minChar: 1, 
			url: null,

			widthDiff: 16, 

			valueField: 'value', 
			labelField: 'label'
		};

		var autocompletePaneTpl = '<div class="pui-autocomplete-panel ui-widget-content ui-corner-all pui-shadow" style="display: none;">' + 
			' <ul class="pui-autocomplete-items pui-autocomplete-list ui-widget-content ui-widget ui-corner-all ui-helper-reset">' + 
			'	<li ng-repeat="one in searchList" data-raw-value="{{one.{0}}}" ' + 
			'		class="pui-autocomplete-item pui-autocomplete-list-item ui-corner-all">{{one.{1}}}</li>' + 
			' </ul>' + 
			'</div>';
		var dropdownPaneTpl ='<div class="pui-dropdown-panel ui-widget-content ui-corner-all ui-helper-hidden pui-shadow">' + 
			' <div class="pui-dropdown-filter-container">' + 
			'	<input type="text" ng-model="querySearch" class="pui-dropdown-filter pui-inputtext ui-widget ui-state-default ui-corner-all" />' + 
			'	<span class="ui-icon ui-icon-search"></span>' + 
			' </div>' + 
			' <div class="pui-dropdown-items-wrapper">' + 
			'	<ul class="pui-dropdown-items pui-dropdown-list ui-widget-content ui-widget ui-corner-all ui-helper-reset">' + 
			'	 <li 	ng-mouseenter="one.isHover = true;" ng-mouseleave="one.isHover = false;" ng-repeat="one in list|filter:querySearch" data-raw-value="{{one.{0}}}" ' + 
			'		ng-class="{\'ui-state-hover\': one.isHover}" ' + 
			'		class="pui-dropdown-item pui-dropdown-list-item ui-corner-all">{{one.{1}}}</li>' + 
			'	</ul>' + 
			' </div>' + 
			'</div>';

		puiAutocompleteHelper.bindDelegateDocumentEvent(attrId);

		return {
			priority: 1001, 
			scope: {
				list: '='
			}, 
			require: 'ngModel', 
			transclude: true, 

			compile: function(el, attrs, transcludeFn){
				return function(scope, el, attrs, ctrl){
					var multiple = !!attrs.multiple;
					var dropdown = !!attrs.dropdown;

					var opts = scope.$eval(attrs.puiAutocomplete) || {};
					opts = angular.extend(angular.copy(defaultOptions), opts);
					console.log('Init puiAutocomplete 4 : ' + attrs.ngModel);
					console.log(opts);
					opts.dropdown = dropdown;

					var cc = countNum++;
					el.attr(attrId, cc);

					if(multiple){
						transcludeFn(scope, function(clone){
							el.wrap('<li class="pui-autocomplete-input-token"></li>');
							el.parent().wrap('<ul class="pui-autocomplete-multiple ui-widget pui-inputtext ui-state-default ui-corner-all"></ul>');
						});
					}else if(dropdown){
						transcludeFn(scope, function(clone){
							el.wrap('<div class="ui-helper-hidden-accessible"></div>');
							var elParent = el.parent();

							opts.width = $(el[0]).width();
							elParent.wrap('<div class="pui-dropdown ui-widget ui-state-default ui-corner-all ui-helper-clearfix" style="width: ' + opts.width + 'px;"></div>');
							elParent.after('<div class="pui-dropdown-trigger ui-state-default ui-corner-right"><span class="ui-icon ui-icon-triangle-1-s"></span></div>');
							elParent.after('<label class="pui-dropdown-label pui-inputtext ui-corner-all" style="width: ' + (opts.width - opts.widthDiff) + 'px;">' + (opts.title || '') + '</label>');
						});
					}

					attrs.$observe('disabled', function(val){
						// disabled -> true
						if(val){
							hidePanel();

							if(multiple){
								var ul = $(el[0]).parent().parent();
								ul.find('.ui-icon-close').hide();
								ul.find('.pui-autocomplete-token-label').addClass('ui-state-disabled');
							}else if(dropdown){
								$(el[0]).parent().parent().addClass('ui-state-disabled');
							}
						}else{
							if(multiple){
								var ul = $(el[0]).parent().parent();
								ul.find('.ui-icon-close').show();
								ul.find('.pui-autocomplete-token-label').removeClass('ui-state-disabled');
							}else if(dropdown){
								$(el[0]).parent().parent().removeClass('ui-state-disabled');
							}
						}
					});

					var tpl = dropdown ? dropdownPaneTpl : autocompletePaneTpl;
					tpl = tpl.format(opts.valueField, opts.labelField);
					var panelNg = $compile(tpl)(scope);
					var panel = $(panelNg[0]);
					panel.attr(attrId, cc);
					panel.find('.pui-dropdown-filter').attr(attrId, cc);
					panel.appendTo($(document.body));

					panel.on('puiAcHide', function(e){
						e.preventDefault();
						e.stopPropagation();

						hidePanel();
					});

					if(multiple){
						panel.on('puiAcDelVal', function(e, targetVal){
							puiAutocompleteHelper.setPanelPosition(panel, $(el[0]), opts);

							// remove one from array
							var targetValList = ctrl.$modelValue || [];
							var index = targetValList.indexOf(targetVal);
							if(index >= 0){
								targetValList.splice(index, 1);

								scope.$apply(function(){
									ctrl.$setViewValue(targetValList);
								});
							}
						});

						ctrl.$render = function(){
							var valList = ctrl.$modelValue || [];
							if(!valList.length)
								return;

							_.each(valList, function(targetVal){
								$(el[0]).parent().before(puiAutocompleteHelper.wrapMultipleLi(targetVal, cc));
								puiAutocompleteHelper.setPanelPosition(panel, $(el[0]), opts);
							});
						};

						// multiple only accept array value
						ctrl.$parsers.unshift(function(value){
							return angular.isArray(value) ? value : ctrl.$modelValue;
						});
					}else if(dropdown){
						ctrl.$render = function(){
							var val = ctrl.$modelValue;
							if(val)
								$(el[0]).parent().parent().find('.pui-dropdown-label').text(val);
						};
					}

					puiAutocompleteHelper.setPanelPosition(panel, $(el[0]), opts);
					puiAutocompleteHelper.bindPanelEvent(panel);

					panel.delegate('li', 'click', function(e){
						chooseCurrent($(e.target));
					});

					var chooseCurrent = function($li){
						panel.hide();

						if(!$li)
							$li = panel.find('li.ui-state-over');

						// no hover, choose first by default
						if(!$li.length)
							$li = panel.find('li').eq(0);

						if(!$li.length)
							return;

						var targetVal = $li.attr('data-raw-value');
						var targetLabel = $li.text();
						if(multiple){
							// add li with show choosed label
							$(el[0]).parent().before(puiAutocompleteHelper.wrapMultipleLi(targetLabel, cc));
							puiAutocompleteHelper.setPanelPosition(panel, $(el[0]), opts);

							// reset input for next time choose from panel
							el.val('');

							var targetValList = ctrl.$modelValue || [];
							targetValList.push(targetVal);

							scope.$apply(function(){
								ctrl.$setViewValue(targetValList);
							});
						}else{
							el.val(targetVal);

							if(dropdown){
								$(el[0]).parent().parent().find('.pui-dropdown-label').text(targetLabel);
							}

							scope.$apply(function(){
								ctrl.$setViewValue(targetVal);
							});
						}
					};

					var checkIfNeedShow = function(val){
						return val.length >= opts.minChar;
					};

					var filterList = function(val){
						if(!scope.list)
							return [];

						// your can change here
						return _.filter(scope.list, function(it){
							var isModelAlready = (!multiple && it[opts.valueField] === ctrl.$modelValue) || 
								(multiple && ctrl.$modelValue && ctrl.$modelValue.indexOf(it[opts.valueField]) >= 0);
							return (it[opts.valueField].indexOf(val) >= 0 || it[opts.labelField].indexOf(val) >= 0)&& !isModelAlready;
						});
					};

					var isActive = false;
					var hidePanel = function(){
						panel.hide();
						isActive = false;
					};

					var showPanel = function(){
						panel.show();
						isActive = true;

						var val = el.val();
						var lastSearchVal = el.data('pui-autocomplete-search-val');

						// same as last input value, need not change search list
						if(val === lastSearchVal)
							return;

						el.data('pui-autocomplete-search-val', val);

						if(!checkIfNeedShow(val))
							return;

						scope.$apply(function(){
							scope.searchList = filterList(val);
						});
					};

					var fnTrigger = function(e){
						e.stopPropagation();

						var keyCode = e.keyCode;
						switch(keyCode){
							// up
							case 38: 
								if(!isActive)
									return false;
								
								puiAutocompleteHelper.focusPrev(panel);
								break;
							// down
							case 40: 
								if(!isActive)
									return false;
								
								puiAutocompleteHelper.focusNext(panel);
								break;
							// return
							case 13: 
								if(!isActive)
									return false;
								
								chooseCurrent();
								break;
							// esc
							case 27: 
								if(!isActive)
									return false;
								
								hidePanel();
								break;
							default:
								showPanel();
								break;
						}
					};

					if(dropdown){
						el.parent().parent().on('click', fnTrigger);
					}else{
						el.on('keydown', function(e){
							var keyCode = e.keyCode;
							// tab
							if(9 === keyCode){
								if(!isActive)
									return false;
								
								chooseCurrent();
							}
						});
						el.on('focus keyup', fnTrigger);
					}
				}; // end return link
			}
		};
	});
	
	md.directive('puiBreadcrumb', function(){
		return {
			scope: {
				list: '=', 
				onLink: '&'
			}, 
			templateUrl: 'tpl/breadcrumb.html', 

			link: function(scope, el, attrs){
				scope.click = function(item, e){
					e.preventDefault();
					scope.onLink({item: item});
				};
			}
		};
	});

	md.directive('puiBreadcrumbTriangle', function(){
		var tpl = '<li class="pui-breadcrumb-chevron ui-icon ui-icon-triangle-1-e"></li>';
		return function(scope, el, attrs){
			$(el[0]).before(tpl);
		};
	});
	
	md.directive('puiButton', function(styleHelper){
		var classType = {
			text: ['text-only', 'text'], 
			icon: ['icon-only', 'icon-left ui-icon'], 
			right: ['text-icon-right', 'icon-right ui-icon'], 
			left: ['text-icon-left', 'icon-left ui-icon']
		};
		
		return {
			compile: function(el, attrs){
				var buildSpan = function(className, text){
					return '<span class="{0}">{1}</span>'.format(className, text);
				};

				el.addClass('pui-button ui-widget ui-state-default ui-corner-all');
				// text|icon|left|right
				var buttonType  = attrs.buttonType || 'text';
				el.addClass('pui-button-' + classType[buttonType][0]);

				styleHelper.addHover($(el[0]));

				if('text' === buttonType){
					el.html(buildSpan('pui-button-' + classType[buttonType][1], el.text()));
				}else{
					var spIcon = buildSpan('pui-button-' + classType[buttonType][1] + ' ' + attrs.icon, '');
					var spText = buildSpan('pui-button-text', el.text() || 'pui-text');
					el.html(spIcon + spText);
				}

				return function(scope, el, attrs){
					attrs.$observe('disabled', function(val){
						el[val ? 'addClass' : 'removeClass']('ui-state-disabled');
					});
				}
			}
		};
	});

  md.directive('puiChx', function(styleHelper){
		var countNum = 0;
		var pre = 'pui_chx_';

		return {
			transclude: true, 
			require: 'ngModel', 
			compile: function(el, attrs, transcludeFn){
				var cc = countNum++;
				var preCurrent = pre + cc + '_';

				var boxTpl = '<div ng-click="{0}check()" class="pui-chkbox-box ui-widget ui-corner-all ui-state-default" ' + 
					'ng-class="{\'ui-state-active\': {0}isChecked, \'ui-state-disabled\': {0}isGray}">' + 
					'<span class="pui-chkbox-icon pui-c" ' + 
					'ng-class="{\'ui-icon\': {0}isChecked, \'ui-icon-check\': {0}isChecked}"></span></div>';
				var $box = $(boxTpl.format(preCurrent));
					
				$(el[0]).after($box);

				styleHelper.addHover($box);

				return function(scope, el, attrs, ctrl){
					transcludeFn(scope, function(clone){
						el.wrap('<div class="ui-helper-hidden-accessible"></div>');
						el.parent().wrap('<div class="pui-chkbox ui-widget"></div>');
						$(el[0]).parent().parent().append($box);
					});

					// angular way -> transclude/compile template use scope, add pre to distinguish
					// because input has no inner html 4 clone and transclude
					var isCheckedProp = '{0}isChecked'.format(preCurrent);
					var isGrayProp = '{0}isGray'.format(preCurrent);
					var checkProp = '{0}check'.format(preCurrent);

					scope[isCheckedProp] = !!ctrl.$modelValue;
					scope[checkProp] = function(){
						// disabled -> true
						if(scope[isGrayProp])
							return;

						scope[isCheckedProp] = !scope[isCheckedProp];
						ctrl.$setViewValue(scope[isCheckedProp]);
					};

					attrs.$observe('disabled', function(val){
						scope[isGrayProp] = val;
					});
				};
			}
		};
	});

	md.directive('puiRadio', function(styleHelper){
		var countNum = 0;
		var pre = 'pui_radio_';

		return {
			transclude: true, 
			require: 'ngModel', 
			compile: function(el, attrs, transcludeFn){
				var cc = countNum++;
				var preCurrent = pre + cc + '_';

				var boxTpl = '<div ng-click="{0}check()" class="pui-radiobutton-box pui-radiobutton-relative ui-widget ui-state-default" ' + 
					'ng-class="{\'ui-state-active\': {0}isChecked, \'ui-state-disabled\': {0}isGray}">' + 
					'<span class="pui-radiobutton-icon pui-c" ' + 
					'ng-class="{\'ui-icon\': {0}isChecked, \'ui-icon-bullet\': {0}isChecked}"></span></div>';
				var $box = $(boxTpl.format(preCurrent));
					
				$(el[0]).after($box);

				styleHelper.addHover($box);

				return function(scope, el, attrs, ctrl){
					transcludeFn(scope, function(clone){
						el.wrap('<div class="ui-helper-hidden-accessible"></div>');
						el.parent().wrap('<div class="pui-radiobutton ui-widget"></div>');
						$(el[0]).parent().parent().append($box);
					});

					var isCheckedProp = '{0}isChecked'.format(preCurrent);
					var isGrayProp = '{0}isGray'.format(preCurrent);
					var checkProp = '{0}check'.format(preCurrent);

					scope[isCheckedProp] = ctrl.$modelValue === el.val();
					scope[checkProp] = function(){
						// disabled -> true
						if(scope[isGrayProp])
							return;

						if(!scope[isCheckedProp])
							scope[isCheckedProp] = true;

						ctrl.$setViewValue(el.val());
					};

					attrs.$observe('disabled', function(val){
						scope[isGrayProp] = val;
					});

					// radio more than one use the same ng model controller
					ctrl.$formatters.push(function(val){
						scope[isCheckedProp] = val === el.val();
					});
				};
			}
		};
	});

	md.directive('puiDialog', function($parse, $compile, styleHelper){
		var zindex = 2010;
		var countNum = 0;
		var defaultOptions = {
			width: 500,
			heightFix: 150,
			id: 'default' 
		};

		var attrId = 'data-pui-dialog-block-id';

		var tpl = '<div class="pui-dialog ui-widget ui-widget-content ui-helper-hidden ui-corner-all pui-shadow ui-draggable ui-resizable">' + 
			' <div class="pui-dialog-titlebar ui-widget-header ui-helper-clearfix ui-corner-top">' + 
			'	<span class="pui-dialog-title">{{title}}</span>' + 
			'	<a class="pui-dialog-titlebar-icon pui-dialog-titlebar-close ui-corner-all" ng-click="close();"><span class="ui-icon ui-icon-close"></span></a>' + 
			'	<a class="pui-dialog-titlebar-icon pui-dialog-titlebar-maximize ui-corner-all" ng-click=""><span class="ui-icon ui-icon-extlink"></span></a>' + 
			'	<a class="pui-dialog-titlebar-icon pui-dialog-titlebar-minimize ui-corner-all" ng-click=""><span class="ui-icon ui-icon-minus"></span></a>' + 
			' </div>';
		
		var tplSuf = ' <div class="ui-resizable-handle ui-resizable-e" style="z-index: 1000;"></div>' + 
			' <div class="ui-resizable-handle ui-resizable-s" style="z-index: 1000;"></div>' + 
			' <div class="ui-resizable-handle ui-resizable-se ui-icon ui-icon-gripsmall-diagonal-se" style="z-index: 1000;"></div>' + 
			'</div>';

		var tplBlock = '<div class="ui-widget-overlay" style="width: 100%; height: 100%;"></div>';

		return {
			scope: true,

			link: function(scope, el, attrs){
				var opts = scope.$eval(attrs.puiDialog) || {};
				opts = angular.extend(angular.copy(defaultOptions), opts);
				if(!opts.showModel)
					return;

				var $modal;
				var subScope;
				scope.$watch(opts.showModel, function(val){
					if(val === true){
						subScope = scope.$new();
						subScope.title = opts.title;
						buildModal();
					}else if(val === false){
						if($modal != null){
							$modal.remove();
							$modal = null;

							if(opts.block){
								$('.ui-widget-overlay').filter(function(){
									return $(this).attr(attrId) === opts.id;
								}).remove();
							}
						}

						if(subScope != null){
							subScope.$destroy();
							subScope = null;
						}
					}
				});

				scope.close = function(){
					$parse(opts.showModel).assign(scope, false);
				};

				var buildModal = function(){
					var tplDialog = tpl + el.html() + tplSuf;
					var modal = $compile(tplDialog)(subScope);

					var left = Math.floor($(window).width() - opts.width) / 2;
					var top = Math.floor($(window).height() - (opts.height || 0) - opts.heightFix) / 2;

					$modal = $(modal[0]);
					if(opts.height){
						$modal.find('.pui-dialog-content').css({height: opts.height, overflow: 'auto'});
					}

					countNum++;
					var dialogIndex = zindex + countNum * 2;
					$modal.css({
						'z-index': dialogIndex, 
						width: opts.width,
						left: left, 
						top: top
					}).appendTo($(document.body)).show();
					styleHelper.delegateHover($modal, '.pui-dialog-titlebar-icon');

					if(opts.block){
						$(tplBlock).attr(attrId, opts.id).css({'z-index': dialogIndex - 1}).appendTo($(document.body));
					}
				};
			}
		};
	});

	md.directive('puiFieldset', function(){
		return {
			compile: function(el, attrs){
				var legend = el.find('legend').eq(0);
				var content = el.find('div').eq(0);

				el.addClass('pui-fieldset ui-widget ui-widget-content ui-corner-all');
				legend.addClass('pui-fieldset-legend ui-corner-all ui-state-default');
				content.addClass('pui-fieldset-content');

				if(attrs.toggle){
					el.addClass('pui-fieldset-toggleable');

					var srcLegend = legend.html();
					var spTpl = '<span class="pui-fieldset-toggler ui-icon ui-icon-minusthick"></span>';
					legend.html(spTpl + srcLegend);

					legend.on('click', function(e){
						var sp = legend.find('span');
						if(sp.hasClass('ui-icon-minusthick')){
							sp.removeClass('ui-icon-minusthick').addClass('ui-icon-plusthick');
							content.hide();
						}else{
							sp.removeClass('ui-icon-plusthick').addClass('ui-icon-minusthick');
							content.show();
						}
					});
				}
			}
		};
	});

	md.directive('puiListbox', function(styleHelper){
		var defaultOptions = {
			valueField: 'value'
		};

		return {
			require: 'ngModel', 
			scope: {
				list: '='
			}, 

			templateUrl: 'tpl/listbox.html', 
			replace: true, 

			link: function(scope, el, attrs, ctrl){
				var opts = scope.$eval(attrs.puiListbox) || {};
				opts = angular.extend(angular.copy(defaultOptions), opts);

				ctrl.$render = function(){
					var list = ctrl.$modelValue;
					if(list && list.length){
						_.each($scope.list, function(it){
							if(list.indexOf(it[opts.valueField]) >= 0){
								it.isChoosed = true;
							}
						});
					}
				};

				attrs.$observe('disabled', function(val){
					el[val ? 'addClass' : 'removeClass']('ui-state-disabled');
				});

				var $el = $(el[0]);
				styleHelper.delegateHover($el, 'li');

				// use ng-click better
				$el.delegate('.pui-listbox-item', 'click', function(e){
					if(attrs.disabled)
						return;

					scope.$apply(function(){
					
					var $li = $(e.target);
					var index = $li.attr('data-index') || 0;

					var item = scope.list[index];
					item.isChoosed = !item.isChoosed;

					// not ctrl key, only choose one
					if(!e.ctrlKey){
						var i = 0, len = scope.list.length, one;
						for(; i < len; i++){
							if(i == index)
								continue;

							one = scope.list[i];
							if(one.isChoosed)
								one.isChoosed = false;
						}
					}

					var choosedList = _.pluck(_.filter(scope.list, function(it){
						return it.isChoosed;
					}), opts.valueField);
					ctrl.$setViewValue(choosedList);

					// $apply end
					}); 
				}); 
			} // end link
		};
	});

	md.directive('puiPaginator', function(styleHelper){
		return {
			scope: {
				pager: '=', 
				onChangePage: '&'
			}, 
			templateUrl: 'tpl/paginator.html', 
			replace: true, 

			compile: function(el, attrs){
				var $el = $(el[0]);
				styleHelper.delegateHover($el, '.pui-paginator-page');

				return function(scope, el, attrs){
					scope.ctrl = {
						isGrayPrev: true, 
						isGrayNext: true
					};

					scope.pageNum = 1;
					scope.pageCount = 0;
					scope.pageNumList = [];

					scope.doChangePage = function(pageNum, event){
						event.preventDefault();

						// check if this page num is available
						if(pageNum < 1 || pageNum > scope.pageCount)
							return;

						scope.onChangePage({pageNum: pageNum});
					};

					var setGray = function(){
						scope.ctrl.isGrayPrev = scope.pageNum <= 1;
						scope.ctrl.isGrayNext = scope.pageNum === scope.pageCount;
					};

					scope.$watch('pager', function(pager){
						if(!pager)
							return;

						var pageSize = pager.pageSize || 10;
						var pageNum = pager.pageNum || 1;
						var totalCount = pager.totalCount || 0;

						scope.pageNum = pageNum;
						scope.pageCount = Math.floor(pager.totalCount / pageSize);
						if(pager.totalCount % pageSize !== 0)
							scope.pageCount++;

						// reuse existed object
						var i = 1;
						for(; i <= scope.pageCount; i++){
							if(scope.pageNumList.length < i)
								scope.pageNumList.push({pageNum: i, isActive: i === pageNum});
							else
								scope.pageNumList[i - 1].isActive = i === pageNum;
						}

						setGray();
					}, true);
				};
			}
		};
	});

	// TODO not finished yet
	md.directive('puiPicklist', function(){
		return {
			scope: {
				sourceList: '=',
				selectedList: '=', 
				sourceTitle: '@', 
				selectedTitle: '@'
			}, 
			templateUrl: 'tpl/picklist.html', 

			link: function(scope, el, attrs){
				$(el[0]).delegate('.pui-picklist-item', 'click', function(e){
					scope.$apply(function(){
					
					var $li = $(e.target);
					var index = $li.attr('data-index') || 0;

					var list = scope[$li.parent().is('.pui-picklist-selected') ? 'selectedList' : 'sourceList'];

					var item = list[index];
					item.isChoosed = !item.isChoosed;

					// not ctrl key, only choose one
					if(!e.ctrlKey){
						var i = 0, len = list.length, one;
						for(; i < len; i++){
							if(i == index)
								continue;

							one = list[i];
							if(one.isChoosed)
								one.isChoosed = false;
						}
					}

					// $apply end
					}); 
				}); 
				
				// type === 1 => selectedList
				scope.up = function(type){
				
				};
				scope.top = function(type){
				
				};
				scope.down = function(type){
				
				};
				scope.bottom = function(type){
				
				};

				// use list copy performance--, but code is cleaner
				var changeList = function(list1, list2, isAll){
					var removedList = isAll ? list1.splice(0, list1.length) : [];
					var i = 0, len = removedList.length;
					for(; i < len; i++){
						list2.push(removedList[i]);
					}
				};

				scope.choose = function(){
					changeList(scope.sourceList, scope.selectedList);
				};

				scope.chooseAll = function(){
					changeList(scope.sourceList, scope.selectedList, true);
				};

				scope.unchoose = function(){
					changeList(scope.selectedList, scope.sourceList);
				};

				scope.unchooseAll = function(){
					changeList(scope.selectedList, scope.sourceList, true);
				};
			}
		};
	});

	md.directive('puiProgressBar', function($parse){
		return {
			require: 'ngModel', 

			compile: function(el, attrs){
				$(el[0]).addClass('pui-progressbar ui-widget ui-widget-content ui-corner-all')
					.append('<div class="pui-progressbar-value ui-widget-header ui-corner-all" style="display: block;"></div>')
					.append('<div class="pui-progressbar-label" style="display: block;"></div>');

				return function(scope, el, attrs, ctrl){
					var labelDiv = $(el[0]).find('.pui-progressbar-label');
					var valueDiv = $(el[0]).find('.pui-progressbar-value');

					ctrl.$render = function(){
						var val = ctrl.$modelValue;
						var width = 0;
						if(val && angular.isNumber(val) && val >= 0 && val <= 100){
							width = val;
						}
						
						labelDiv.text(width + '%');
						valueDiv.css({width: width + '%'});
					};
				}; // end link
			} 
		};
	});

	md.directive('puiRating', function(){
		var countNum = 0;
		var pre = 'pui_rating_';

		var tpl = '<div class="pui-rating">' + 
		 '<div ng-repeat="one in {0}list" ' + 
			'ng-click="{0}setNum($index);" ' + 
			'ng-class="{\'pui-rating-star-on\': one.isOn}" class="pui-rating-star">' + 
			'<a></a>' + 
		 '</div>' + 
		'</div>';

		return {
			require: 'ngModel', 
			compile: function(el, attrs){
				var cc = countNum++;
				var preCurrent = pre + cc + '_';

				var listProp = '{0}list'.format(preCurrent);
				var setNumProp = '{0}setNum'.format(preCurrent);

				el.html(tpl.format(preCurrent));

				return function(scope, el, attrs, ctrl){
					var initNum = attrs.num ? parseInt(attrs.num) : 5;
					scope[listProp] = _.map(_.range(0, initNum), function(it){
						return {isOn: false};
					});

					// ngModel should in an object
					scope.$watch(attrs.ngModel, function(num){
						if(!angular.isNumber(num))
							num = 0;

						_.each(scope[listProp], function(it, i){
							it.isOn = i < num;
						});
					});

					scope[setNumProp] = function(index){
						if(attrs.disabled)
							return;

						ctrl.$setViewValue(index + 1);
					};

					attrs.$observe('disabled', function(val){
						el[val ? 'addClass' : 'removeClass']('ui-state-disabled');
					});
				}
			}
		};
	});

	md.directive('puiSpinner', function(){
		$(document).delegate('.pui-spinner-button', 'click', function(e){
			var but = $(e.target);
			if(!but.hasClass('pui-spinner-button'))
				but = but.closest('.pui-spinner-button');

			var input = but.siblings('input');
			input.triggerHandler('spinner-' + (but.hasClass('pui-spinner-up') ? 'up' : 'down'));
		});
		return {
			require: 'ngModel', 
			transclude: true, 
			compile: function(el, attrs, transcludeFn){
				el.addClass('pui-spinner-input');

				return function(scope, el, attrs, ctrl){
					transcludeFn(scope, function(clone){
						el.wrap('<span class="pui-spinner ui-widget ui-corner-all"></span>');
						el.after('<a class="pui-spinner-button pui-spinner-up ui-corner-tr ui-button ui-widget ui-state-default ui-button-text-only">' + 
							'<span class="ui-button-text"><span class="ui-icon ui-icon-triangle-1-n"></span></span></a>' + 
							'<a class="pui-spinner-button pui-spinner-down ui-corner-br ui-button ui-widget ui-state-default ui-button-text-only">' + 
							'<span class="ui-button-text"><span class="ui-icon ui-icon-triangle-1-s"></span></span></a>');
					});

					var opts = scope.$eval(attrs.puiSpinner) || {};
					var step = opts.step || 1;

					var fnChangeVal = function(isUp){
						var val = ctrl.$viewValue ? Number(ctrl.$viewValue) : 0;
						var targetVal = isUp ? val + step : val - step;
						el.val(targetVal);
						scope.$apply(function(){
							ctrl.$setViewValue(targetVal);
						});
					};

					var $el = $(el[0]);
					$el.on('spinner-up', function(){
						if(attrs.disabled)
							return;

						fnChangeVal(true);
					}).on('spinner-down', function(){
						if(attrs.disabled)
							return;

						fnChangeVal(false);
					});

					attrs.$observe('disabled', function(val){
						$el.closest('.pui-spinner')[val ? 'addClass' : 'removeClass']('ui-state-disabled');
						$el.siblings('a')[val ? 'addClass' : 'removeClass']('ui-state-disabled');
					});
				};
			}
		};
	});

	md.directive('puiSplitbutton', function($parse, styleHelper){
		var countNum = 0;
		var attrId = 'data-pui-splitbutton-id';
		var zindex = 1010;

		$(document).click(function(e){
			var target = $(e.target);
			if(!target.is('.pui-splitbutton-menubutton'))
				target = target.closest('.pui-splitbutton-menubutton');

			if(target.length){
				$('.pui-menu').each(function(){
					var menu = $(this);
					if(menu.attr(attrId) !== target.attr(attrId))
						menu.addClass('ng-hide');
					else
						menu.toggleClass('ng-hide');
				});
			}else{
				$('.pui-menu').addClass('ng-hide');
			}
		});

		return {
			scope: {
				list: '=', 
				label: '@', 
				onClick: '&'
			}, 
			templateUrl: 'tpl/splitbutton.html', 
			compile: function(el, attrs){
				var cc = countNum++;

				var $el = $(el[0]);
				var offset = $el.offset();
				var h = $el.height();

				el.find('.pui-splitbutton-menubutton').attr(attrId, cc);
				el.find('.pui-menu').attr(attrId, cc).css({
					'z-index': zindex, 
					top: offset.top + h, 
					left: offset.left
				});

				styleHelper.addHover($(el[0]).find('button,li'));
			}
		};
	});

	md.directive('puiSticky', function(){
		return {
			compile: function(el, attrs){
				var widthFix = 10;

				var $el = $(el[0]);
				var offset = $el.offset();
				var w = $el.width() - widthFix;
				var h = $el.height();

				var fixed = false;

				var $win = $(window);
				$win.on('scroll', function(){
					if($win.scrollTop() > offset.top){
						if(fixed)
							return;

						el.css({
							'position': 'fixed',
							'top': 0,
							'z-index': 10000,
							'width': w + 'px'
						}).addClass('pui-shadow ui-sticky');
						$('<div class="ui-sticky-ghost"></div>').height(h).insertBefore($(el[0]));

						fixed = true;
					}else{
						if(!fixed)
							return;

						el.css({
							'position': 'static',
							'top': 'auto',
							'z-index': 10000,
							'width': w + 'px'
						}).removeClass('pui-shadow ui-sticky');
						$(el[0]).prev('.ui-sticky-ghost').remove();

						fixed = false;
					}
				}); // end scroll event binding
			}
		};
	});

	md.directive('puiTabview', function(){
		return {
			scope: true, 
			transclude: true, 

			templateUrl: 'tpl/tabview.html', 

			controller: function($scope, $element, $attrs){
				var opts = $scope.$eval($attrs.puiTabview) || {};
				var activeIndex = opts.activeIndex || 0;

				var panes = $scope.panes = [];

				this.focus = function(pane){
					_.each(panes, function(it){
						it.isSelected = false;
					});
					pane.isSelected = true;
				};

				this.add = function(pane){
					panes.push(pane);
					if(activeIndex === panes.length - 1){
						pane.isSelected = true;
					}
				};
			},

			link: function(scope, el, attrs, ctrl){
				scope.isLeft = !!attrs.left;
				scope.selectPane = function(index){
					ctrl.focus(scope.panes[index]);
				};
				scope.removePane = function(index){
					scope.panes.splice(index, 1);
				};
			}
		};
	});

	md.directive('puiTabviewPane', function(){
		return {
			require: '^puiTabview',

			scope: {
				title: '@'
			}, 
			transclude: true, 

			templateUrl: 'tpl/tabview-pane.html', 

			link: function(scope, el, attrs, ctrl){
				scope.isClosable = !!attrs.closable;
				ctrl.add(scope);
			}
		};
	});
	
  md.directive('puiTextfield', function(){
		return {
			compile: function(el, attrs){
				el.addClass('pui-inputtext ui-widget ui-state-default ui-corner-all');
				el.on('mouseenter', function(){
					el.addClass('ui-state-hover');
				}).on('mouseleave', function(){
					el.removeClass('ui-state-hover');
				}).on('focus', function(){
					el.addClass('ui-state-focus');
				}).on('blur', function(){
					el.removeClass('ui-state-focus');
				});

				return function(scope, el, attrs){
					attrs.$observe('disabled', function(val){
						el[val ? 'addClass' : 'removeClass']('ui-state-disabled');
					});
				};
			}
		};
	});

	md.directive('puiTooltip', function($parse){
		var countNum = 0;
		var attrId = 'data-pui-tooltip-id';
		var defaultOptions = {
			posLeftFix: 10, 
			posTopFix: 5
		};

		return {
			link: function(scope, el, attrs){
				var opts = scope.$eval(attrs.puiTooltip) || {};
				opts = angular.extend(angular.copy(defaultOptions), opts);

				var titleGetter = function(){
					return opts.title || $parse(opts.titleModel)(scope);
				};

				var cc = countNum++;
				var tooltip = $('<div class="pui-tooltip ui-widget ui-widget-content ui-corner-all pui-shadow" />').attr(attrId, cc).appendTo($(document.body));
				el.attr(attrId, cc);

				el.on(opts.showEvent || 'mouseenter', function(e){
					var pos = {left: e.pageX + opts.posLeftFix, top: e.pageY + opts.posTopFix};
					tooltip.html(titleGetter()).css(pos).show();
				}).on(opts.hideEvent || 'mouseleave', function(){
					tooltip.hide();
				});
			}
		};
	});

	md.service('pagiFilter', function(){
		this.getListInPage = function(list, pager, ctrl){
			var orderByField = ctrl.orderByField;
			var isUp = ctrl.isUp;

			var beginIndex = (pager.pageNum - 1) * pager.pageSize;

			var sortedList = list;
			if(orderByField !== null && isUp !== null){
				list.sort(function(a, b){
					var val1 = a[orderByField];
					var val2 = b[orderByField];
					var diff = 0;
					if(angular.isNumber(val1) && angular.isNumber(val2)){
						diff = val1 - val2;
					}else if(angular.isDate(val1) && angular.isDate(val2)){
						diff = val1.getTime() - val2.getTime();
					}else{
						diff = ('' + val1).localeCompare('' + val2);
					}

					return isUp ? diff : -diff;
				});
			}

			return sortedList.slice(beginIndex, beginIndex + pager.pageSize);
		};
	});

	md.directive('puiPagiDatatable', function(pagiFilter){
		return {
			scope: {
				headers: '=', 
				list: '=',
			
				title: '@'
			},

			templateUrl: 'tpl/pagi-datatable.html', 

			link: function(scope, el, attrs){
				scope.ctrl = {};
				scope.ctrl.orderByField = null;
				scope.ctrl.isUp = null;

				scope.pager = {totalCount: scope.list.length, pageNum: 1, 
					pageSize: attrs.pageSize || 10};

				scope.sort = function(index){
					var header = scope.headers[index];
					header.isUp = !header.isUp;
					header.isDown = !header.isUp;

					header.isOn = true;

					// set other header no sort state
					var i = 0, len = scope.headers.length, one;
					for(; i < len; i++){
						one = scope.headers[i];
						if(i !== index)
							one.isUp = one.isDown = one.isOn = false;
					}

					scope.ctrl.orderByField = header.field;
					scope.ctrl.isUp = header.isUp;
				};

				scope.$watch(function(){
					return {ctrl: scope.ctrl, pager: scope.pager};
				}, function(obj){
					scope.searchList = pagiFilter.getListInPage(scope.list, obj.pager, obj.ctrl);
				}, true);

				scope.changePage = function(pageNum){
					scope.pager.pageNum = pageNum
				};
			} // end link
		};
	});

	md.service('puiTreeNodeHelper', function(){
		this.generate = function(list){
			var ll = this.transferList2Tree(list);

			var r = '<div class="pui-tree ui-widget ui-widget-content ui-corner-all">';
			r += '<ul class="pui-tree-container">';

			var i = 0, len = ll.length, one;
			for(; i < len; i++){
				one = ll[i];
				r += this.generateNode(one);
			}

			r += '</ul>';
			r += '</div>';
			return r;
		};

		this.generateNode = function(item){
			var isLeaf = !item.subList.length;

			var spParent = '<span class="pui-tree-toggler ui-icon ui-icon-triangle-1-s"></span>' + 
				'<span class="pui-treenode-icon ui-icon ui-icon-folder-open"></span>';
			var spLeaf = '<span class="pui-treenode-leaf-icon"></span>' + 
				'<span class="pui-treenode-icon ui-icon ' + (item.icon || '') + '"></span>';

			var tplPre = '<li data-id="' + item.id + '" class="pui-treenode ' + (isLeaf ? 'pui-treenode-leaf' : 'pui-treenode-parent pui-treenode-expanded') + 
				'"><span class="pui-treenode-content pui-treenode-selectable">' + 
				(isLeaf ? spLeaf : spParent) + 
				'<span class="pui-treenode-label ui-corner-all">' + item.label + '</span>' + 
				'</span>';
			var tplSuf = '</li>';

			var inner = '';
			if(!isLeaf){
				inner += '<ul class="pui-treenode-children">';
				var i = 0, len = item.subList.length, one;
				for(; i < len; i++){
					one = item.subList[i];
					inner += this.generateNode(one);
				}
				inner += '</ul>';
			}

			return tplPre + inner + tplSuf;
		};

		this.transferList2Tree = function(list, pid){
			pid = pid || 0;
			var ll = _.filter(list, function(it){
				return pid === it.pid;
			});

			if(ll.length){
				var that = this;
				_.each(ll, function(it){
					it.subList = that.transferList2Tree(list, it.id);
				});
			}

			return ll;
		};
	});

	md.directive('puiTree', function(styleHelper, puiTreeNodeHelper){
		return {
			scope: {
				list: '=', 
				onChoose: '&'
			}, 

			link: function(scope, el, attrs){
				var $el = $(el[0]);
				styleHelper.delegateHover($el, '.pui-treenode-label');

				// use string add, not ng-repeat nested
				$el.delegate('.pui-treenode', 'click', function(e){
					var li = $(e.target);

					var isToggle = false;
					if(li.hasClass('pui-tree-toggler'))
						isToggle = true;
					if(!li.hasClass('pui-treenode'))
						li = li.closest('.pui-treenode');

					// expand or collapse
					if(isToggle){
						var collapseSp = li.find('span').eq(0).find('span').eq(1);
						var collapseToggler = li.find('span').eq(0).find('span').eq(0);
						var ul = li.find('ul');
						if(collapseSp.hasClass('ui-icon-folder-open')){
							collapseToggler.removeClass('ui-icon-triangle-1-s').addClass('ui-icon-triangle-1-e');
							collapseSp.removeClass('ui-icon-folder-open').addClass('ui-icon-folder-collapsed');
							ul.hide();
						}else{
							collapseToggler.removeClass('ui-icon-triangle-1-e').addClass('ui-icon-triangle-1-s');
							collapseSp.removeClass('ui-icon-folder-collapsedui-icon-folder-open').addClass('ui-icon-folder-open');
							ul.show();
						}
					}else{
						scope.$apply(function(){
							var id = parseInt(li.attr('data-id'));
							var item = _.findWhere(scope.list, {id: id});
							scope.onChoose({item: item});
						});
					}
				});

				scope.$watch('list', function(list){
					var tpl = puiTreeNodeHelper.generate(list);
					$el.empty().html(tpl);
				}, true);
			}
		};
	});

})(window, document, angular);
